package com.springboot.frame.comm;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.sql.DataSource;

import com.springboot.frame.comm.config.GlobalConfig;
import com.springboot.frame.comm.config.StrategyConfig;
import com.springboot.frame.comm.database.Database;
import com.springboot.frame.comm.database.SimpleDatasource;
import com.springboot.frame.comm.database.provider.SqlProvider;
import com.springboot.frame.comm.database.registry.TypeRegistry;
import com.springboot.frame.comm.domain.Column;
import com.springboot.frame.comm.domain.FileTemplate;
import com.springboot.frame.comm.domain.Table;
import com.springboot.frame.comm.domain.Template;
import com.springboot.frame.comm.exception.CodeGeneratorException;
import com.springboot.frame.comm.render.FreemarkerRender;
import com.springboot.frame.comm.util.CodeUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * 数据库到实体
 *
 * @author madman
 */
@Slf4j
public final class AutoCodeGenerator {

    /**
     * 通用数据源
     */
    private DataSource dataSource;
    /**
     * 全局配置
     */
    private GlobalConfig globalConfig = GlobalConfig.INSTANCE;
    /**
     * 策略配置
     */
    private StrategyConfig strategyConfig = new StrategyConfig();
    /**
     * 模板信息
     */
    private List<Template> templates = new ArrayList<>();

    public AutoCodeGenerator(String driverClass, String url, String username, String password) {
        this.dataSource = new SimpleDatasource(driverClass, url, username, password);
    }

    public AutoCodeGenerator(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 创建文件夹
     */
    public static void mkDir(String fileDir) {
        File file = new File(fileDir);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    public AutoCodeGenerator globalConfig(GlobalConfig globalConfig) {
        this.globalConfig = globalConfig;
        return this;
    }

    public AutoCodeGenerator strategyConfig(StrategyConfig strategyConfig) {
        this.strategyConfig = strategyConfig;
        return this;
    }

    private List<Table> tables(String parent) {
        Database database = autoSelectDatabase(dataSource);
        SqlProvider sqlProvider = database.getSqlProvider();
        List<Table> tables = new ArrayList<>();
        String[] tablePrefix = strategyConfig.getTablePrefix();
        String[] includeTable = strategyConfig.getIncludeTable();
        try (Connection conn = dataSource.getConnection();
             ResultSet tableRs = conn.createStatement().executeQuery(sqlProvider.tables())) {
            while (tableRs.next()) {
                String tableJdbcName = tableRs.getString(sqlProvider.tableName());
                if (ArrayUtils.isNotEmpty(includeTable) && !ArrayUtils
                        .contains(includeTable, tableJdbcName)) {
                    continue;
                }
                Table table = new Table();
                table.setSuperClass(globalConfig.getSuperClass());
                table.setJdbcName(tableJdbcName);
                table.setComment(tableRs.getString(sqlProvider.tableComment()));
                String tableJavaName = CodeUtils.removePrefixAndCamel(tableJdbcName, tablePrefix);
                Set<String> importPackages = table.getImportPackages();
                if (!StringUtils.equals(CodeUtils.camelToUnderline(tableJavaName), tableJdbcName)) {
                    table.setIsTableAnnotation(Boolean.TRUE);
                }
                if (globalConfig.isEnableLombok()) {
                    importPackages.add("lombok.Data");
                }
                String javaName = StringUtils.capitalize(tableJavaName);
                table.setJavaName(javaName);
                fields(table, database, conn);
                Map<String, FileTemplate> fileTemplates = new HashMap<>(templates.size());
                for (Template template : templates) {
                    String filePackage = String.format(template.getOutFilePackage(), parent);
                    String fileDir =
                            globalConfig.getOutputDir() + filePackage.replaceAll("\\.", "\\" + File.separator);
                    mkDir(fileDir);
                    String fileName = String.format(template.getOutFilename(), javaName);
                    String filePath = fileDir + File.separator + fileName + template.getOutFileType();
                    fileTemplates
                            .put(template.getName(),
                                    new FileTemplate(template.getRender(), fileName, filePackage, filePath));
                }
                table.setFileTemplates(fileTemplates);
                tables.add(table);
            }
        } catch (SQLException e) {
            throw new CodeGeneratorException("执行sql语句出错", e);
        }
        return tables;
    }

    private void fields(Table table, Database database, Connection conn) throws SQLException {
        List<String> superColumns = table.getSuperColumns();
        String[] columnPrefix = strategyConfig.getColumnPrefix();
        boolean autoToCamel = globalConfig.isAutoToCamel();
        SqlProvider sqlProvider = database.getSqlProvider();
        TypeRegistry typeRegistry = database.getTypeRegistry();
        Set<String> importPackages = table.getImportPackages();
        List<Column> columns = new ArrayList<>();
        try (Statement columnStatement = conn.createStatement();
             ResultSet columnRs = columnStatement
                     .executeQuery(sqlProvider.tableFields(table.getJdbcName()))) {
            while (columnRs.next()) {
                Column column = new Column();
                String jdbcName = columnRs.getString(sqlProvider.fieldName());
                if (superColumns.contains(jdbcName)) {
                    continue;
                }
                if ("PRI".equals(columnRs.getString(sqlProvider.primaryKey()))) {
                    column.setIsPrimaryKey(true);
                    table.setKeyColumn(column);
                }
                column.setIsNullAble(("NO".equals(columnRs.getString("NULL")) ? false : true));
                String jdbcType = columnRs.getString(sqlProvider.fieldType());
                String property = CodeUtils.removePrefixAndCamel(jdbcName, columnPrefix);
                column.setJdbcName(jdbcName);
                column.setJdbcType(jdbcType);
                column.setComment(columnRs.getString(sqlProvider.fieldComment()));
                column.setProperty(property);
                column.setPropertyType(typeRegistry.getJavaType(jdbcType.toLowerCase()), importPackages);
                column.setCapitalProperty(StringUtils.capitalize(property));
                String removePrefix = CodeUtils.removePrefix(jdbcName, columnPrefix);
                boolean hasPrefix = !StringUtils.equals(removePrefix, jdbcName);
                if (hasPrefix || (!hasPrefix && !autoToCamel && !StringUtils.equals(property, jdbcName))) {
                    column.setIsColumnAnnotation(Boolean.TRUE, importPackages);
                }
                columns.add(column);
            }
            table.setColumns(columns);
        }
    }

    /**
     * 生成代码
     */
    public void execute(String parentPackage) {
        log.info("=========================开始生成文件！！！==========================");
        boolean fileOverride = globalConfig.isFileOverride();
        Map<String, Object> renderData = new HashMap<>(3);
        renderData.put("enableLombok", globalConfig.isEnableLombok());
        renderData.put("enableCache", globalConfig.isEnableCache());
        for (Table table : tables(parentPackage)) {
            for (Entry<String, FileTemplate> entry : table.getFileTemplates().entrySet()) {
                FileTemplate fileTemplate = entry.getValue();
                renderData.put("table", table);
                File file = new File(fileTemplate.getFilePath());
                if (fileOverride || !file.exists()) {
                    try (FileOutputStream fos = new FileOutputStream(file);
                         OutputStreamWriter out = new OutputStreamWriter(fos, "UTF-8");
                         BufferedWriter writer = new BufferedWriter(out)) {
                        fileTemplate.render(renderData, writer);
                        log.info("生成文件" + file.getPath());
                    } catch (IOException e) {
                        log.info("生成文件失败" + e);
                    }
                }
            }
        }
        log.info("==========================文件生成完成！！！==========================");
    }

    /**
     * 模板文件生成
     *
     * @param parentPackage
     */
    public void executeDefault(String parentPackage) {
        addTemplate(new Template("entity", new FreemarkerRender("entity.java.ftl"), "%s.entity", "%s",
                ".java"));
        addTemplate(
                new Template("mapper", new FreemarkerRender("mapper.java.ftl"), "%s.mapper", "%sMapper",
                        ".java"));
        addTemplate(
                new Template("mapperXml", new FreemarkerRender("mapperXml.xml.ftl"), "%s.mapper.xml",
                        "%sMapper", ".xml"));
        addTemplate(
                new Template("service", new FreemarkerRender("service.java.ftl"), "%s.service", "%sService",
                        ".java"));
        addTemplate(
                new Template("serviceImpl", new FreemarkerRender("serviceImpl.java.ftl"), "%s.service.impl",
                        "%sServiceImpl",
                        ".java"));
        addTemplate(
                new Template("controller", new FreemarkerRender("controller.java.ftl"), "%s.controller", "%sController",
                        ".java"));
        addTemplate(
                new Template("crudController", new FreemarkerRender("crud.controller.java.ftl"), "%s.controller", "CrudController",
                        ".java"));
        execute(parentPackage);
    }

    public AutoCodeGenerator addTemplate(Template template) {
        templates.add(template);
        return this;
    }

    /**
     * 自动选择数据库
     *
     * @return 数据库枚举
     */
    private Database autoSelectDatabase(DataSource dataSource) {
        try (Connection connection = dataSource.getConnection()) {
            String url = connection.getMetaData().getURL();
            if (url.startsWith("jdbc:mysql")) {
                return Database.MYSQL;
            } else if (url.startsWith("jdbc:oracle")) {
                return Database.ORACLE;
            } else if (url.startsWith("jdbc:h2")) {
                return Database.H2;
            } else {
                throw new CodeGeneratorException("暂时只支持mysql,oracle");
            }
        } catch (SQLException e) {
            throw new CodeGeneratorException("获取连接失败", e);
        }
    }

}
